/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.datasource.crypt;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import org.obiba.magma.Datasource;
import org.obiba.magma.MagmaRuntimeException;
import org.obiba.magma.crypt.KeyProvider;
import org.obiba.magma.crypt.MagmaCryptRuntimeException;
import org.obiba.magma.type.BinaryType;
import org.obiba.magma.type.TextType;
/**
* Creates a {@link DatasourceCipherFactory} that creates {@code Cipher} instances using a newly generated
* {@code SecretKey}. The secret key is encrypted using the {@code PublicKey} returned by the {@link KeyProvider}
* instance. A {@code Cipher} instance is initialised to obtain a {@code AlgorithmParameters} instance.
* <p/>
* The following attributes are added to the datasource to allow decryption using the corresponding {@code PrivateKey}:
* <ul>
* <li>{@link CipherAttributeConstants#SECRET_KEY}</li>
* <li>{@link CipherAttributeConstants#SECRET_KEY_ALGORITHM}</li>
* <li>{@link CipherAttributeConstants#CIPHER_TRANSFORMATION}</li>
* <li>{@link CipherAttributeConstants#CIPHER_ALGORITHM_PARAMETERS}</li>
* <li>{@link CipherAttributeConstants#CIPHER_IV}</li>
* <li>{@link CipherAttributeConstants#PUBLIC_KEY}</li>
* <li>{@link CipherAttributeConstants#PUBLIC_KEY_FORMAT}</li>
* <li>{@link CipherAttributeConstants#PUBLIC_KEY_ALGORITHM}</li>
* </ul>
*/
public class GeneratedSecretKeyDatasourceEncryptionStrategy implements DatasourceEncryptionStrategy {
private String algorithm = "AES";
private String mode = "CFB";
// CFB Mode supports no padding.
private String padding = "NoPadding";
// Larger key size requires installing "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy
// Files" which can be downloaded from Sun
private int keySize = 128;
private transient KeyProvider keyProvider;
//
// DatasourceEncryptionStrategy Methods
//
@Override
public void setKeyProvider(KeyProvider keyProvider) {
this.keyProvider = keyProvider;
}
@Override
public boolean canDecryptExistingDatasource() {
return false;
}
@Override
public DatasourceCipherFactory createDatasourceCipherFactory(Datasource ds) {
// If there's already a secret key in the datasource, then stop. We cannot read the contents.
if(ds.hasAttribute(CipherAttributeConstants.SECRET_KEY)) {
throw new MagmaCryptRuntimeException(
"Datasource '" + ds.getName() + "' is encrypted and cannot be read without the proper decryption key.");
}
try {
String transformation = getTransformation();
SecretKey sk = getSecretKey(ds);
AlgorithmParameters parameters = initialiseParameters(ds, transformation, sk);
return new DefaultDatasourceCipherFactory(transformation, sk, parameters);
} catch(GeneralSecurityException | IOException e) {
throw new MagmaRuntimeException(e);
}
}
//
// Methods
//
public void setMode(String mode) {
this.mode = mode;
}
public void setPadding(String padding) {
this.padding = padding;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getTransformation() {
StringBuilder sb = new StringBuilder(algorithm);
if(mode != null) {
sb.append('/').append(mode);
if(padding != null) {
sb.append('/').append(padding);
}
}
return sb.toString();
}
public void setKeySize(int keySize) {
this.keySize = keySize;
}
private SecretKey getSecretKey(Datasource datasource) throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
keyGen.init(keySize);
SecretKey sk = keyGen.generateKey();
byte[] keyData = wrapKey(datasource, sk);
datasource.setAttributeValue(CipherAttributeConstants.SECRET_KEY, BinaryType.get().valueOf(keyData));
datasource.setAttributeValue(CipherAttributeConstants.SECRET_KEY_ALGORITHM, TextType.get().valueOf(algorithm));
return sk;
}
private AlgorithmParameters initialiseParameters(Datasource ds, String transformation, SecretKey sk)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException {
// Initialise a Cipher. This causes the creation of the cipher's AlgorithmParameters.
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, sk);
ds.setAttributeValue(CipherAttributeConstants.CIPHER_TRANSFORMATION, TextType.get().valueOf(transformation));
ds.setAttributeValue(CipherAttributeConstants.CIPHER_ALGORITHM_PARAMETERS,
BinaryType.get().valueOf(cipher.getParameters().getEncoded()));
if(cipher.getIV() != null) {
ds.setAttributeValue(CipherAttributeConstants.CIPHER_IV, BinaryType.get().valueOf(cipher.getIV()));
}
return cipher.getParameters();
}
private byte[] wrapKey(Datasource datasource, SecretKey sk) {
try {
PublicKey publicKey = keyProvider.getPublicKey(datasource);
if(publicKey.getEncoded() != null) {
datasource
.setAttributeValue(CipherAttributeConstants.PUBLIC_KEY, BinaryType.get().valueOf(publicKey.getEncoded()));
datasource.setAttributeValue(CipherAttributeConstants.PUBLIC_KEY_FORMAT,
TextType.get().valueOf(publicKey.getFormat()));
datasource.setAttributeValue(CipherAttributeConstants.PUBLIC_KEY_ALGORITHM,
TextType.get().valueOf(publicKey.getAlgorithm()));
}
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.WRAP_MODE, publicKey);
return cipher.wrap(sk);
} catch(GeneralSecurityException e) {
throw new MagmaRuntimeException(e);
}
}
}